package Verifier.Cryptography;

public class PRG {
	// the hash function which is used by this PRG
	HashFunction hash;


	/**
	 * Class constructor
	 * 
	 * @param hash the HushFunction which we will use in the PRG
	 * */
	public PRG(HashFunction hash){
		// save the hash to use with this PRG
		this.hash = hash;
	}


	/**
	 * @return The required number of bits in the seed
	 * */
	public int NumberOfBitsSeed(){
		// the number of bits of the seed is the number of bytes for the hash * 8 (cause there are 8 bits in each byte)
		return hash.NumberOfBytes()*8;
	}



	/**
	 * Generates a random number longer than the seed.
	 * 
	 * If the seed length is != this.NumberOfBitsSeed(), returns null
	 * @param byteSeed byte array contains the seed
	 * @param firstBit determines where the sub-array should begin
	 * @param lastBit determines where the sub-array should end
	 * 
	 * @return a byte array of length lastBit-firstBit
	 * */
	public byte[] BuildingRandom(byte[] byteSeed,int firstBit,int lastBit){
		// check that the seed fits our requirements
		if(byteSeed.length != hash.NumberOfBytes()) {
			return null;
		}
		// find out which bytes are the first and the last we need to generate
		int firstByte = firstBit/8;
		int lastByte = lastBit/8+1;
		// find out which chunk of data(which i-s) we should start with and which one is our last
		int iLowerBound = firstByte/hash.NumberOfBytes();
		int iUpperBound = lastByte/hash.NumberOfBytes()+1;
		// create an array to hold all the chunks, and other temporary arrays for further use
		byte PRNumbersArray[] = new byte[(iUpperBound-iLowerBound)*hash.NumberOfBytes()];
		byte[] tempArray;
		byte[] returnArray;

		int firstByteOffset;
		// create all the chunks from the lower and until the upper
		for (int i=iLowerBound; i < iUpperBound; i++) {
			byte[] iSeed = makeSeed(byteSeed,i);
			System.arraycopy(hash.HashDigest(iSeed), 0,PRNumbersArray , (i-iLowerBound)*hash.NumberOfBytes(), hash.NumberOfBytes());
		}
		// from here on, we should remove the unneeded bytes which got generated from the lower bound chunk, and the ones from the upper bound chunk
		firstByteOffset = firstByte % hash.NumberOfBytes();

		/*Now, hopefully, returnArray contains the relevant bytes*/
		tempArray = getSubArray(PRNumbersArray, firstByteOffset, lastByte-firstByte);

		// now, tempArray holds the cleared array which hold the correct bytes except for some bits from the start and the end which we should remove
		// to do so we work in 2 phases:
		// 1. shift all the bits in the array to the right
		// 2. shift all the bits in the array to the left
		
		
		// to include the last bit in the calculations
		lastBit += 1;
		// calculate a masking bytes to clear the first and last bytes from the unneeded bits 
		byte firstByteMask = (byte)(0xFF << (8 - firstBit%8));
		byte lastByteMask = (byte)~(0xFF << (8 - lastBit%8));
		if (0 == lastBit%8) { lastByteMask = 0; }
		tempArray[0] = (byte)(tempArray[0] & ~firstByteMask);
		tempArray[tempArray.length - 1] = (byte)(tempArray[tempArray.length - 1] & ~lastByteMask);
		
		// now, shift all the bits in the array to the right.
		// to do so, add another byte, to prevent problems with applying the sign to the shifted bytes
		byte[] tempArray2 = new byte[tempArray.length + 1];
		System.arraycopy(tempArray, 0, tempArray2, 1, tempArray.length);
		// here, we calculate masks for getting the bits from the current byte which should overflow to the next one
		int numBytesLeft = (8 - lastBit%8) % 8;
		byte maskByte2 = (byte)(0xFF >> (8 - numBytesLeft));
		byte maskByte = (byte)(maskByte2 << (8 - numBytesLeft));
		byte savedBits = 0;
		// for each byte, from the start
		for (int i = 0; i < tempArray2.length; i++) {
			// save the savedBits of the previous byte
			byte tempBits = savedBits;
			// save the bits which need to be saved from the current byte
			savedBits = (byte)((tempArray2[i] & maskByte2) << (8 - numBytesLeft));
			// shift the byte and then apply the saved bits of the previous byte
			tempArray2[i] = (byte)((tempArray2[i] >> numBytesLeft) & (~maskByte));
			tempArray2[i] = (byte)(tempArray2[i] | tempBits);
		}
		// now, if the number of bytes is less than the number of bytes in tempArray2, we should remove the first byte which we added to prevent the 
		// sign applying, otherwise, return tempArray2
		int totalNumberOfBytes = (int)Math.ceil((lastBit - firstBit) / 8.0);
		
		if (totalNumberOfBytes < tempArray2.length) {
			returnArray = new byte[totalNumberOfBytes];
			System.arraycopy(tempArray2, tempArray2.length - totalNumberOfBytes, returnArray, 0, totalNumberOfBytes);
		} else {
			returnArray = tempArray2;
		}
		
		// now, we shift all the bits to the left
		// here, we calculate masks for getting the bits from the current byte which should overflow to the next one
		numBytesLeft = (8 * returnArray.length) - (lastBit - firstBit);
		maskByte = (byte)~(0xFF << numBytesLeft);
		maskByte2 = (byte)(maskByte << (8 - numBytesLeft));
		savedBits = 0;
		// for each byte, from the end
		for (int i = returnArray.length - 1; i >= 0 ; i--) {
			// save the savedBits of the previous byte
			byte tempBits = savedBits;
			// save the bits which need to be saved from the current byte
			savedBits = (byte)(((returnArray[i] & maskByte2) >> (8 - numBytesLeft)) & maskByte);
			// shift the byte and then apply the saved bits of the previous byte
			returnArray[i] = (byte)(returnArray[i] << numBytesLeft);
			returnArray[i] = (byte)(returnArray[i] | tempBits);
		}
		// return the array
		return returnArray;
	}

	/**
	 * Generates the ith concatation.
	 * 
	 * @param byteSeed 	the byte representation of the seed
	 * @param i 		the integer counter
	 * 
	 * @return		byte array containing the param which should be hashed
	 * */
	private byte[] makeSeed(byte[] byteSeed, int i) {
		// create the i-th seed for the PRG
		// create an array of bytes for the number i
		byte[] integerCounter = getBytes(i);
		// now, concatenate the arrays which hold the seed and the representation of i
		byte[] c = new byte[byteSeed.length+integerCounter.length];

		System.arraycopy(byteSeed, 0, c, 0, byteSeed.length);
		System.arraycopy(integerCounter, 0, c, byteSeed.length, integerCounter.length);
		// return the concatenated array
		return c;
	}


	/**
	 * Generates the byte representation of an integer
	 * 
	 * @param i The integer we  want the byte representation of
	 * 
	 * */
	private byte[] getBytes(int value) {
		// converts an int into an array of bytes
		byte[] b = new byte[4];
		for (int j = 0; j < 4; j++) {
			int offset = (b.length - 1 - j) * 8;
			b[j] = (byte) ((value >>> offset) & 0xFF);
		}
		return b;
	}

	/**
	 * Gets the sub-array of length <tt>length</tt> from <tt>array</tt>
	 * that starts at <tt>offset</tt>.
	 */
	private static byte[] getSubArray(byte[] array, int offset, int length) {
		// return a sub-array of array, by copying it into a smaller array from offset to length
		byte[] result = new byte[length];
		System.arraycopy(array, offset, result, 0, length);
		return result;
	}
}

